﻿using System;
using System.Xml;
using Notoric.Model.Internal;
using Notoric.Model.Packaging;
using Notoric.Model.Validation;

namespace Notoric.Model.Elements.CWNotation
{
    /// <summary>
    /// Describes a staff.
    /// </summary>
    public class StaffDescriptor : ModelElement
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="StaffDescriptor"/> instance.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="XmlPackagePart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="CWPartData"/>
        /// </para>
        /// <para>
        /// The model element that directly owns this element.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="packagePart"/> or <paramref name="owner"/> is <see langword="null"/>.
        /// </exception>
        internal StaffDescriptor(XmlPackagePart packagePart, CWPartData owner)
            : base(packagePart, owner)
        {
            if (owner == null)
                throw new ArgumentNullException("owner");

            DefaultClefPosition = 2;
            DefaultClefType = ClefType.G;
            DefaultColor = new Color(0, 0, 0, 255);
            DefaultHeight = 0.0;
            Lines = 0;
        }

        /// <summary>
        /// Creates a new <see cref="StaffDescriptor"/> instance.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="XmlPackagePart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="CWPartData"/>
        /// </para>
        /// <para>
        /// The model element that directly owns this element.
        /// </para>
        /// </param>
        /// <param name="element">
        /// <para>
        /// Type: <see cref="XmlElement"/>
        /// </para>
        /// <para>
        /// The XML element that holds the data of this object.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Any of the arguments is <see langword="null"/>.
        /// </exception>
        internal StaffDescriptor(XmlPackagePart packagePart, CWPartData owner, XmlElement element)
            : base(packagePart, owner, element)
        {
            if (owner == null)
                throw new ArgumentNullException("owner");

            clefPositionAttribute = element.Attributes[PackageVocabulary.ClefPositionAttribute];
            clefTypeAttribute = element.Attributes[PackageVocabulary.ClefTypeAttribute];
            colorAttribute = element.Attributes[PackageVocabulary.ColorAttribute];
            heightAttribute = element.Attributes[PackageVocabulary.HeightAttribute];
            linesAttribute = element.Attributes[PackageVocabulary.LinesAttribute];
        }

        #endregion

        #region ModelElement Members

        /// <inheritdoc/>
        protected override XmlElement CreateObjectElement(XmlDocument document)
        {
            var element = document.CreateElement(
                PackageVocabulary.PackageNamespacePrefix,
                PackageVocabulary.StaffDescriptorElementName,
                PackageVocabulary.PackageNamespaceUri
                );

            clefPositionAttribute = document.CreateAttribute(PackageVocabulary.ClefPositionAttribute);
            clefTypeAttribute = document.CreateAttribute(PackageVocabulary.ClefTypeAttribute);
            colorAttribute = document.CreateAttribute(PackageVocabulary.ColorAttribute);
            heightAttribute = document.CreateAttribute(PackageVocabulary.HeightAttribute);
            linesAttribute = document.CreateAttribute(PackageVocabulary.LinesAttribute);

            element.Attributes.Append(clefPositionAttribute);
            element.Attributes.Append(clefTypeAttribute);
            element.Attributes.Append(colorAttribute);
            element.Attributes.Append(heightAttribute);
            element.Attributes.Append(linesAttribute);

            return element;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the default color of the staff.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Color"/>
        /// </para>
        /// <para>
        /// The default color of the staff.
        /// </para>
        /// </value>
        public Color DefaultColor
        {
            get { return Color.Parse(colorAttribute.Value); }
            set
            {
                colorAttribute.Value = value.ToString();
                RaisePropertyChanged("DefaultColor");
            }
        }

        /// <summary>
        /// Gets or sets the default clef position.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Int32"/>
        /// </para>
        /// <para>
        /// The staff position of the default clef for the described staff.
        /// </para>
        /// </value>
        public int DefaultClefPosition
        {
            get { return int.Parse(clefPositionAttribute.Value, PackageVocabulary.PackageCulture.NumberFormat); }
            set
            {
                clefPositionAttribute.Value = value.ToString(PackageVocabulary.PackageCulture.NumberFormat);

                RaisePropertyChanged("DefaultClefPosition");
            }
        }

        /// <summary>
        /// Gets or sets the default type of the clef.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="ClefType"/>
        /// </para>
        /// <para>
        /// The type of the default clef for the described staff.
        /// </para>
        /// </value>
        public ClefType DefaultClefType
        {
            get { return (ClefType)Enum.Parse(typeof(ClefType), clefTypeAttribute.Value); }
            set
            {
                clefTypeAttribute.Value = value.ToString();

                RaisePropertyChanged("DefaultClefType");
            }
        }

        /// <summary>
        /// Gets or sets the default height of the staff.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Double"/>
        /// </para>
        /// <para>
        /// The default height (in centimeters) of the staff.
        /// </para>
        /// </value>
        /// <exception cref="ValidationException">
        /// The <paramref name="value"/> is less than 0.0.
        /// </exception>
        /// TODO: Write the validation rules.
        public double DefaultHeight
        {
            get { return double.Parse(heightAttribute.Value, PackageVocabulary.PackageCulture.NumberFormat); }
            set
            {
                ValidateAndThrow("DefaultHeight", value);

                heightAttribute.Value = value.ToString(PackageVocabulary.PackageCulture.NumberFormat);
                RaisePropertyChanged("DefaultHeight");
            }
        }

        /// <summary>
        /// Gets or sets the number of staff lines.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Byte"/>
        /// </para>
        /// <para>
        /// The number of staff lines. 
        /// In common western music notation the usual number is 5.
        /// </para>
        /// </value>
        public byte Lines
        {
            get { return byte.Parse(linesAttribute.Value, PackageVocabulary.PackageCulture.NumberFormat); }
            set
            {
                linesAttribute.Value = value.ToString(PackageVocabulary.PackageCulture.NumberFormat);
                RaisePropertyChanged("Lines");
            }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the value of the <see cref="DefaultClefPosition"/> property.
        /// </summary>
        private XmlAttribute clefPositionAttribute;
        /// <summary>
        /// Holds the value of the <see cref="DefaultClefType"/> property.
        /// </summary>
        private XmlAttribute clefTypeAttribute;
        /// <summary>
        /// Holds the value of the <see cref="DefaultColor"/> property.
        /// </summary>
        private XmlAttribute colorAttribute;
        /// <summary>
        /// Holds the value of the <see cref="DefaultHeight"/> property.
        /// </summary>
        private XmlAttribute heightAttribute;
        /// <summary>
        /// Holds the value of the <see cref="Lines"/> property.
        /// </summary>
        private XmlAttribute linesAttribute;

        #endregion
    }
}
